using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Text.RegularExpressions;

namespace Diffluxum.CCScoring
{
    public class CCScoringCoordinator
    {
        const string r = @"^log(?<year>\d{4})(?<month>\d{2})(?<day>\d{2})(?<hour>\d{2})(?<minute>\d{2})(?<second>\d{2})";
        private static readonly Regex _dateMatcher = new Regex(r, RegexOptions.Compiled);

        private readonly DirectoryInfo _directory;

        private readonly ITimeProvider _timeProvider;
        private readonly IUserScorerFactory _scorerFactory;
        private readonly IBuildFileParser _fileParser;
        private readonly IGradingRule _gradingRule;
        IBrokenBuildsSpanFactory _buildsSpanFactory;

        public CCScoringCoordinator(
            string filePath,
            ITimeProvider timeProvider,
            IUserScorerFactory scorerFactory,
            IBuildFileParser fileParser,
            IGradingRule gradingRule,
            IBrokenBuildsSpanFactory buildsSpanFactory)
        {
            _directory = new DirectoryInfo(filePath);
            _timeProvider = timeProvider;
            _scorerFactory = scorerFactory;
            _fileParser = fileParser;
            _gradingRule = gradingRule;
            _buildsSpanFactory = buildsSpanFactory;
        }

        public IList<IUserScorer> Score(DateTime start, DateTime stop)
        {

            if (stop < start)
            {
                throw new ArgumentOutOfRangeException("stop",
                    String.Format("stop must be later than start. (start={0}, stop={1})",
                    start,
                    stop));
            }

            List<BuildResult> buildResults = GetBuildResults(start, stop);

            IEnumerable<IUserScorer> userScores = ScoreBuilds(buildResults);

            List<IUserScorer> results = new List<IUserScorer>(GradeUsers(userScores));

            return results;
        }




        #region private methods

        private IEnumerable<IUserScorer> ScoreBuilds(List<BuildResult> buildResults)
        {
            Dictionary<string, IUserScorer> scores = new Dictionary<string, IUserScorer>();
            IBrokenBuildsSpan brokenBuildsSpan = null;

            foreach (BuildResult buildResult in buildResults)
            {
                // handle green build
                if (buildResult.BuildSuccess)
                {
                    if (brokenBuildsSpan != null)
                    {
                        brokenBuildsSpan.BuildFixed(buildResult);
                        TellInterestedUsers(brokenBuildsSpan, brokenBuildsSpan.InvolvedUsers, scores);
                        brokenBuildsSpan = null;
                    }
                    else
                    {
                        TellInterestedUsers(buildResult, buildResult.ResponsibleUsers, scores);
                    }

                    continue;
                }

                // handle red build
                if (brokenBuildsSpan == null)
                {
                    brokenBuildsSpan = _buildsSpanFactory.CreateNewSpan(buildResult);
                }
                else
                {
                    brokenBuildsSpan.AddBreakingBuild(buildResult);
                }
            }

            if (brokenBuildsSpan != null)
            {
                brokenBuildsSpan.LastsUntil(_timeProvider.CurrentTime);
                TellInterestedUsers(brokenBuildsSpan, brokenBuildsSpan.InvolvedUsers, scores);
            }

            return scores.Values;
        }

        private IEnumerable<string> GetUnion(IEnumerable<string> a, IEnumerable<string> b)
        {
            List<string> union = new List<string>(a);

            foreach (string value in b)
            {
                if (!union.Contains(value))
                {
                    union.Add(value);
                }
            }

            return union;
        }

        private void TellInterestedUsers(BuildResult buildResult, IEnumerable<string> interestedUsers, Dictionary<string, IUserScorer> scores)
        {
            foreach (string userName in interestedUsers)
            {
                IUserScorer scorer;

                if (!scores.TryGetValue(userName, out scorer))
                {
                    scorer = _scorerFactory.CreateScorer(userName);
                    scores.Add(userName, scorer);
                }

                scorer.AddSuccessfulBuild(buildResult);
            }
        }

        private void TellInterestedUsers(IBrokenBuildsSpan brokenSpan, IEnumerable<string> interestedUsers, Dictionary<string, IUserScorer> scores)
        {
            foreach (string userName in interestedUsers)
            {
                IUserScorer scorer;

                if (!scores.TryGetValue(userName, out scorer))
                {
                    scorer = _scorerFactory.CreateScorer(userName);
                    scores.Add(userName, scorer);
                }

                scorer.AddBrokenBuildsSpan(brokenSpan);
            }
        }

        private IEnumerable<IUserScorer> GradeUsers(IEnumerable<IUserScorer> scores)
        {
            foreach (IUserScorer score in scores)
            {
                _gradingRule.GradeUser(score);
            }
            return scores;
        }

        private List<BuildResult> GetBuildResults(DateTime start, DateTime stop)
        {
            FileInfo[] files = _directory.GetFiles("log*.xml");

            List<BuildResult> buildResults = new List<BuildResult>();

            // Parse all build results
            foreach (FileInfo file in files)
            {
                if (!WithinDateRange(start, stop, file))
                {
                    continue;
                }

                BuildResult buildState = _fileParser.ParseBuildFile(file.FullName);
                buildResults.Add(buildState);
            }
            return buildResults;
        }

        private bool WithinDateRange(DateTime start, DateTime stop, FileInfo file)
        {
            Match m = _dateMatcher.Match(file.Name);
            if (!m.Success)
            {
                return false;
            }

            DateTime date = new DateTime(
                int.Parse(m.Groups["year"].Value),
                int.Parse(m.Groups["month"].Value),
                int.Parse(m.Groups["day"].Value),
                int.Parse(m.Groups["hour"].Value),
                int.Parse(m.Groups["minute"].Value),
                int.Parse(m.Groups["second"].Value));

            return (date >= start) && (date <= stop);

        }

        #endregion

    }


}
